Marc's Notes

How to animate a Radar Plot

21.02.2022

In this short guide I will show you how to animate a Radar Plot with matplotlib in Python.

Example

At the end of this guide you will be able to create radar plot animations like this one:

Import modules

The only modules needed are numpy and matplotlib. Import them like this:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

Data

For this guide, I am creating some artificial data to make the data structure a bit more visible:

length = 250

data = [
    [np.sin(i*0.06) for i in range(0, length)],
    [np.cos(i*0.06) for i in range(0, length)],
    [np.sin(i*0.06) for i in range(0, length)],
    [np.cos(i*0.06) for i in range(0, length)],
    [np.sin(i*0.06) for i in range(0, length)]
]

data is a list of the different features that you want to plot. Every feature list contains the individual datapoints that will be plotted at frame i. This means all lists have to be the same length.

Now, the first feature list will need to be copied to the end of the data list. This will ensure that the radar plot will be a closed polygon. The first and last datapoint will overlap and therefore close the shape.

data = [*data, data[0]]

# This works too 
data.append(data[0])

To achieve this, we can use the syntax *expression to spread out the list and append the first entry.

From the Python documentation:

If the syntax *expression appears in the function call, expression must evaluate to an iterable. Elements from these iterables are treated as if they were additional positional arguments. For the callf(x1, x2, *y, x3, x4), if _y_ evaluates to a sequence _y1_, …, _yM_, this is equivalent to a call with M+4 positional arguments _x1_, _x2_, _y1_, …, _yM_, _x3_, _x4_.

Labels

To create labels for every feature in the data list, create a new list and apply the same procedure with the spread operator.

labels = ["Label 1", "Label 2", "Label 3", "Label 4", "Label 5"]
labels = [*labels, labels[0]]

# This works too 
labels.append(labels[0])

The last step, before plotting begins, will be to calculate evenly distributed positions for the labels like this:

# Get len(labels) equal distance points on a circle
label_loc = np.linspace(0, 2*np.pi, num=len(labels))

Initialize plot

For every animation in matplotlib an initial plot (using the first datapoints) is needed. To create a radar plot it is important to set the projection method to polar.

fig, ax = plt.subplots(figsize=(10, 10), subplot_kw={'projection': 'polar'})

In a radar plot the x-values will correspond to the positions on the circle (0-360 degrees) and the y-values are the distances from the middle point of the circle.

Therefore the x-values will be the label positions and the y-values will be the datapoints. For the initial plot the first datapoints will be selected using a list comprehension.

radar_ln = ax.plot(label_loc, [d[0] for d in data], label="Legend")

The labels themselves will be placed at the previously calculated positions around the circle by converting the radians to degrees and setting the labels in the set_thetagrids method.

ax.set_thetagrids(np.degrees(label_loc), labels=labels)
ax.legend(loc="lower left", bbox_to_anchor=(0.95, 0.95))

The limits in a radar plot can be changed too:

ax.set_rmax(2)
ax.set_rmin(-2)

Update function

Animating a plot with matplotlib will always include an update(i) function which will update the plot with new data for every frame i. In this case the new data will again be selected with a list comprehension (the same way the plot was initialized).

def update(i, radar_ln, label_loc, data):
    radar_ln[0].set_data(label_loc, [d[i] for d in data])
    return radar_ln

Rendering and saving the animation

Finally, it is only a matter of assigning the correct variables to FuncAnimation before running the save() method.

ani = FuncAnimation(fig, update, frames=len(data[0]), fargs=(radar_ln, label_loc, data), interval=40)

ani.save("animation.mp4", dpi=300)